/*
* Copyright (c) 2008 Thierry Coppey, EPFL
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Thierry Coppey nor EPFL, nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THIERRY COPPEY AND EPFL ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THIERRY COPPEY OR EPFL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "SnifferPacket.h"

NSMutableDictionary *name_ipp=nil; // ip protocols
NSMutableDictionary *name_tcp=nil; // tcp services
NSMutableDictionary *name_udp=nil; // udp services

@implementation SnifferPacket
// ---------------------------------------------------------------------------------------------
// Services name dictionnaries

+(void) initialize {
	name_ipp=[[NSMutableDictionary alloc] init];
	name_tcp=[[NSMutableDictionary alloc] init];
	name_udp=[[NSMutableDictionary alloc] init];
	char buf[256],*p; FILE * fd;
	if (fd=fopen("/etc/services","r")) while(fgets(buf,256,fd)) { if (buf[0]=='#') continue;
		if (p=strpbrk(buf," \t")) { *p=0; do p++; while(strchr(" \t",*p));
			if (strstr(p,"tcp")) [name_tcp setObject:[NSString stringWithFormat:@"%s",buf] forKey:[NSNumber numberWithInt:atoi(p)]];
			else if (strstr(p,"udp")) [name_udp setObject:[NSString stringWithFormat:@"%s",buf] forKey:[NSNumber numberWithInt:atoi(p)]];
		}	
	}
	fclose(fd);
	if (fd=fopen("/etc/protocols","r")) while(fgets(buf,256,fd)) { if (buf[0]=='#') continue;
		if (p=strpbrk(buf," \t")) { *p=0; do p++; while(strchr(" \t",*p));
			[name_ipp setObject:[NSString stringWithFormat:@"%s",buf] forKey:[NSNumber numberWithInt:atoi(p)]];
		}	
	}
	fclose(fd);
}
+(void)cleanup {
	if (name_ipp) [name_ipp release];
	if (name_tcp) [name_tcp release];
	if (name_udp) [name_udp release];
}

// ---------------------------------------------------------------------------------------------
// Initialization
-(id)init { if (self=[super init]) { packet=0; p_len=0; data=0; d_len=0; } return self; }
-(void)dealloc { free(packet); [super dealloc]; }

-(id)initWith:(const void*)bytes {
	if (self=[super init]) {
		int l;
		// copy packet data
		h_bpf=(struct bpf_hdr*)bytes; p_len=h_bpf->bh_hdrlen+h_bpf->bh_caplen;
		if (!ETHER_IS_VALID_LEN(h_bpf->bh_datalen)||!(packet=malloc(p_len))) { [self release]; return nil; }
		memcpy(packet,bytes,p_len); h_bpf=packet; h_eth=(void*)h_bpf+h_bpf->bh_hdrlen;
		data=(void*)h_eth+ETHER_HDR_LEN; d_len=h_bpf->bh_caplen-(ETHER_HDR_LEN+ETHER_CRC_LEN);
		// packet analysis
		h_ip=0; h_tcp=0; h_udp=0; h_icmp=0; // h_arp=0; ...
		switch (ntohs(h_eth->ether_type)) {
			case ETHERTYPE_PUP: break;
			case ETHERTYPE_IP:
				h_ip=(void*)data;
				l=(h_ip->ip_hl*4); if (l>d_len) d_len=0; else { data+=l; d_len-=l; }
				switch(h_ip->ip_p) {
					case IPPROTO_TCP:
						h_tcp=(void*)data; l=(h_tcp->th_off*4);
						if (l>d_len) d_len=0; else { d_len-=l; data+=l; } break;
					case IPPROTO_UDP:
						h_udp=(void*)data; l=sizeof(struct udphdr);
						if (l>d_len) d_len=0; else { d_len-=l; data+=l; } break;
					case IPPROTO_ICMP:
						h_icmp=(void*)data; break;
				}
			break;
			case ETHERTYPE_ARP: break;
			case ETHERTYPE_REVARP: break;
			case ETHERTYPE_VLAN: break;
			case ETHERTYPE_IPV6: break;
			case ETHERTYPE_LOOPBACK: break;
		}
	}
	return self;
}

+(id)packetWith:(const void *)bytes { return [[SnifferPacket alloc] initWith:bytes]; }

// ---------------------------------------------------------------------------------------------
// Useful function

- (NSString*) hostMAC:(bool)dst {
	u_char* h=dst?h_eth->ether_dhost:h_eth->ether_shost;
	return [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x",h[0],h[1],h[2],h[3],h[4],h[5]];
}

- (NSString*) hostAddr:(bool)dst { if (!h_ip) return @""; return [NSString stringWithFormat:@"%s",inet_ntoa(dst?h_ip->ip_dst:h_ip->ip_src)]; }

- (NSString*) hostName:(bool)dst {
	struct hostent *h=0;
	// TODO : CACHE SOMEHOW THE NAMES FOR HOST
	if (h_ip) h=gethostbyaddr(dst?&(h_ip->ip_dst):&(h_ip->ip_src),sizeof(struct in_addr),AF_INET);
	// other protocols
	return h?[NSString stringWithFormat:@"%s",h->h_name]:@"";
}

- (NSString*) hostPort:(bool)dst {
	if (!h_tcp&&!h_udp) return @"";
	int n=ntohs(h_tcp?(dst?h_tcp->th_dport:h_tcp->th_sport):(dst?h_udp->uh_dport:h_udp->uh_sport));
	NSString* s=h_tcp?[name_tcp objectForKey:[NSNumber numberWithInt:n]]:[name_udp objectForKey:[NSNumber numberWithInt:n]];
	return s?[NSString stringWithFormat:@":%d (%@)",n,s]:[NSString stringWithFormat:@":%d",n];
}

// ---------------------------------------------------------------------------------------------
// Packet properties

- (NSString*) transport {
	if (h_ip) {
		if (h_tcp) return @"TCP/IP";
		else if (h_udp) return @"UDP/IP";
		else if (h_icmp) return @"ICMP";
		else return @"IP";
	} else switch(ntohs(h_eth->ether_type)) {
		case ETHERTYPE_PUP: return @"PUP";
		case ETHERTYPE_ARP: return @"ARP";
		case ETHERTYPE_REVARP: return @"ReverseARP";
		case ETHERTYPE_VLAN: return @"VLAN";
		case ETHERTYPE_IPV6: return @"IPv6";
		case ETHERTYPE_LOOPBACK: return @"Loopback";
		default: return @"Unknown";
	}
}

- (NSString*) source { if (h_ip) return [NSString stringWithFormat:@"%@%@",[self hostAddr:false],[self hostPort:false]]; else return [self hostMAC:false]; }
- (NSString*) destination { if (h_ip) return [NSString stringWithFormat:@"%@%@",[self hostAddr:true],[self hostPort:true]]; else return [self hostMAC:true]; }
- (NSString*) protocol {
	
	// TODO : generally, take the min number

	if (!h_tcp&&!h_udp) return [self transport]; int n=ntohs(h_tcp?(h_tcp->th_dport):(h_udp->uh_dport));
	NSString* s=h_tcp?[name_tcp objectForKey:[NSNumber numberWithInt:n]]:[name_udp objectForKey:[NSNumber numberWithInt:n]];
	return s?[s uppercaseString]:@"Unknown";
}
- (NSString*) time:(bool)full {
	time_t c=h_bpf->bh_tstamp.tv_sec; struct tm *t=localtime(&c);
	if (full) return [NSString stringWithFormat:@"%04d.%02d.%02d %02d:%02d:%02d.%06d",t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec,h_bpf->bh_tstamp.tv_usec];
	else return [NSString stringWithFormat:@"%02d:%02d:%02d.%03d",t->tm_hour,t->tm_min,t->tm_sec,h_bpf->bh_tstamp.tv_usec/1000];
}

- (NSData*) content:(SnifferTreeNode*)tree {
	[tree removeAllChild];
	
	// TODO : ENSURE ALL HEADERS ARE IN PACKET
	
	SnifferTreeNode* n=[SnifferTreeNode nodeWith:@"BPF":@""]; struct tm *t=localtime(&(h_bpf->bh_tstamp.tv_sec));
	[n addChildWith:@"Temps":[NSString stringWithFormat:@"%04d.%02d.%02d %02d:%02d:%02d.%06d",
		t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec,h_bpf->bh_tstamp.tv_usec]];
	[n addChildWith:@"Longueur":[[NSString stringWithFormat:@"%d octets",h_bpf->bh_caplen] retain]];
	[tree addChild:n]; [n release]; n=[SnifferTreeNode nodeWith:@"Ethernet":@""]; u_char* crc=packet+p_len-ETHER_CRC_LEN;
	[n addChildWith:@"Source":[self hostMAC:false]];
	[n addChildWith:@"Destination":[self hostMAC:true]];
	[n addChildWith:@"CRC (Checksum)":[NSString stringWithFormat:@"%02x:%02x:%02x:%02x",crc[0],crc[1],crc[2],crc[3]]];
	[tree addChild:n]; [n release];
	if (h_ip) [self decodeIP:tree];

	// TODO : other decodings
	/*
		case ETHERTYPE_PUP: o=@"PUP";
		case ETHERTYPE_ARP: o=@"ARP";
		case ETHERTYPE_REVARP: o=@"ReverseARP";
		case ETHERTYPE_VLAN: o=@"VLAN";
		case ETHERTYPE_IPV6: o=@"IPv6";
		case ETHERTYPE_LOOPBACK: o=@"Loopback";
	*/

	return d_len>0?[NSData dataWithBytes:data length:d_len]:nil;
};

// ---------------------------------------------------------------------------------------------
// Decoding helpers

-(void)decodeIP:(SnifferTreeNode*)tree {
	SnifferTreeNode* n=[SnifferTreeNode nodeWith:@"IP":[NSString stringWithFormat:@"v%d",h_ip->ip_v]];
	NSMutableString *o=[NSMutableString stringWithString:@""];
	if (h_ip->ip_tos&IPTOS_LOWDELAY) [o appendString:@", LowDelay"];
	if (h_ip->ip_tos&IPTOS_THROUGHPUT) [o appendString:@", ThroughPut"];
	if (h_ip->ip_tos&IPTOS_RELIABILITY) [o appendString:@", Reliability"];
	if (h_ip->ip_tos&IPTOS_MINCOST) [o appendString:@", MinCost"];
	[n addChildWith:@"TOS (Type Of Service)":([o length]>2?[[o substringFromIndex:2] retain]:@"Default")]; [o release];
	o=nil; switch (h_ip->ip_tos&IPTOS_ECN_MASK) {
		case IPTOS_ECN_NOTECT: o=@"not-ECT"; break;
		case IPTOS_ECN_ECT1: o=@"ECN-capable(1)"; break;
		case IPTOS_ECN_ECT0: o=@"ECN-capable(0)"; break;
		case IPTOS_ECN_CE: o=@"congestion experienced"; break;
	}
	if (o) [n addChildWith:@"ECN (Explicit Congestion Notification)":o];
	o=nil; switch (h_ip->ip_tos&IPTOS_PREC_NETCONTROL) {
		case IPTOS_PREC_NETCONTROL: o=@"Network Control"; break;
		case IPTOS_PREC_INTERNETCONTROL: o=@"Internetwork Control"; break;
		case IPTOS_PREC_CRITIC_ECP: o=@"CriticECP"; break;
		case IPTOS_PREC_FLASHOVERRIDE: o=@"FlashOverride"; break;
		case IPTOS_PREC_FLASH: o=@"Flash"; break;
		case IPTOS_PREC_IMMEDIATE: o=@"Immediate"; break;
		case IPTOS_PREC_PRIORITY: o=@"Piority"; break;
		case IPTOS_PREC_ROUTINE: o=@"Routine"; break;
	}
	if (o) [n addChildWith:@"IP precedence":o];
	[n addChildWith:@"Total length":[NSString stringWithFormat:@"%d",ntohs(h_ip->ip_len)]];
	[n addChildWith:@"Identification":[NSString stringWithFormat:@"%d",ntohs(h_ip->ip_id)]];
	[n addChildWith:@"Fragment offset":[NSString stringWithFormat:@"%d",ntohs((h_ip->ip_off|IP_OFFMASK)-IP_OFFMASK)]];
	o=[NSMutableString stringWithString:@""];
	if (h_ip->ip_tos&IP_RF) [o appendString:@", ReservedFragment"];
	if (h_ip->ip_tos&IP_DF) [o appendString:@", DontFragment"];
	if (h_ip->ip_tos&IP_MF) [o appendString:@", MoreFragment"];
	[n addChildWith:@"Fragmentation Options":([o length]>2?[o substringFromIndex:2]:@"None")]; [o release];
	[n addChildWith:@"Time To Live (TTL)":[NSString stringWithFormat:@"%d",h_ip->ip_ttl]];
	[n addChildWith:@"Protocol":[NSString stringWithFormat:@"%@ (0x%02X)",[[name_ipp objectForKey:[NSNumber numberWithInt:h_ip->ip_p]] uppercaseString],h_ip->ip_p]];
	[n addChildWith:@"Checksum":[NSString stringWithFormat:@"0x%04X",ntohs(h_ip->ip_sum)]];
	[n addChildWith:@"Source":[NSString stringWithFormat:@"%s",inet_ntoa(h_ip->ip_src)]];
	[n addChildWith:@"Destination":[NSString stringWithFormat:@"%s",inet_ntoa(h_ip->ip_dst)]];
	
	// TODO : decode options

	[tree addChild:n]; [n release];

	if (h_tcp) [self decodeTCP:tree];
	if (h_udp) [self decodeUDP:tree];
	if (h_icmp) [self decodeICMP:tree];
	
	// TODO : other decodings
}

-(void)decodeTCP:(SnifferTreeNode*)tree {
	SnifferTreeNode* n=[SnifferTreeNode nodeWith:@"TCP":@""];
	[n addChildWith:@"Port source":[NSString stringWithFormat:@"%d",ntohs(h_tcp->th_sport)]];
	[n addChildWith:@"Port destination":[NSString stringWithFormat:@"%d",ntohs(h_tcp->th_dport)]];
	[n addChildWith:@"SEQ (Sequence number)":[NSString stringWithFormat:@"%u",ntohl (h_tcp->th_seq)]];
	[n addChildWith:@"ACK (Acknowledgement)":[NSString stringWithFormat:@"%u",ntohl (h_tcp->th_ack)]];
	NSMutableString* o=[NSMutableString stringWithString:@""];
	if (h_tcp->th_flags&TH_FIN) [o appendString:@", FIN"];
	if (h_tcp->th_flags&TH_SYN) [o appendString:@", SYN"];
	if (h_tcp->th_flags&TH_RST) [o appendString:@", RST"];
	if (h_tcp->th_flags&TH_PUSH) [o appendString:@", PUSH"];
	if (h_tcp->th_flags&TH_ACK) [o appendString:@", ACK"];
	if (h_tcp->th_flags&TH_URG) [o appendString:@", URG"];
	if (h_tcp->th_flags&TH_ECE) [o appendString:@", ECE"];
	if (h_tcp->th_flags&TH_CWR) [o appendString:@", CRW"];
	[n addChildWith:@"Flags":([o length]>2?[o substringFromIndex:2]:@"None")]; [o release];
	[n addChildWith:@"Window size":[NSString stringWithFormat:@"%d",ntohs(h_tcp->th_win)]];
	[n addChildWith:@"Checksum":[NSString stringWithFormat:@"0x%04X",ntohs(h_tcp->th_sum)]];
	[n addChildWith:@"Urgent pointer":[NSString stringWithFormat:@"%d",ntohs(h_tcp->th_urp)]];

	// TODO : TCP OPTIONS

	[tree addChild:n]; [n release];

	// TODO : DECODE FURTHER

}

-(void)decodeUDP:(SnifferTreeNode*)tree {
	SnifferTreeNode* n=[SnifferTreeNode nodeWith:@"UDP":@""];
	[n addChildWith:@"Port source":[NSString stringWithFormat:@"%d",ntohs(h_udp->uh_sport)]];
	[n addChildWith:@"Port destination":[NSString stringWithFormat:@"%d",ntohs(h_udp->uh_dport)]];
	[n addChildWith:@"Length":[NSString stringWithFormat:@"%d",ntohs(h_udp->uh_ulen)]];
	[n addChildWith:@"Checksum":[NSString stringWithFormat:@"0x%04X",ntohs(h_udp->uh_sum)]];
	[tree addChild:n]; [n release];

	// TODO : DECODE FURTHER

}

-(void)decodeICMP:(SnifferTreeNode*)tree {
	SnifferTreeNode* n=[SnifferTreeNode nodeWith:@"ICMP":@""];
	NSMutableString *o=nil; switch (h_icmp->icmp_type) {
		case 0: o=@"Echo reply"; break;				case 3: o=@"Destination unreachable"; break;		case 4: o=@"Source quench"; break;
		case 5: o=@"Redirect"; break;				case 6: o=@"Alternate host address"; break;		case 8: o=@"Echo request"; break;
		case 9: o=@"Router advertisement"; break;		case 10: o=@"Router solicitation"; break;		case 11: o=@"Time exceeded"; break;
		case 12: o=@"Parameter problem"; break;			case 13: o=@"Timestamp request"; break;			case 14: o=@"Timestamp reply"; break;
		case 15: o=@"Information request"; break;		case 16: o=@"Information reply"; break;			case 17: o=@"Address mask request"; break;
		case 18: o=@"Address mask reply"; break;		case 19: o=@"Reserved for security";			case 30: o=@"Traceroute"; break;
		case 31: o=@"Datagram conversion error"; break;		case 32: o=@"Mobile host redirect"; break;		case 33: o=@"IPv6 Where-Are-You"; break;
		case 34: o=@"IPv6 I-Am-Here"; break;			case 35: o=@"Mobile registration request"; break;	case 36: o=@"Mobile registration reply"; break;
		case 37: o=@"Domain name request"; break;		case 38: o=@"Domain name reply"; break;			case 39: o=@"SKIP discovery protocol"; break;
		case 40: o=@"Photuris, security failures"; break;	case 41: o=@"Experimental mobility protocols"; break;
		default: o=(h_icmp->icmp_type>=20&&h_icmp->icmp_type<=29)?@"Reserved for robustness experiment":@"Reserved";
	}
	if (o) [n addChildWith:@"Type":o];
	o=nil; switch (h_icmp->icmp_type) {
		case 3: switch (h_icmp->icmp_code) {
			case 0: o=@"Destination network unreachable"; break;	case 1: o=@"Destination host unreachable"; break;
			case 2: o=@"Destination protocol unreachable"; break;	case 3: o=@"Destination port unreachable"; break;
			case 4: o=@"Fragmentation required (DF set)"; break;	case 5: o=@"Source route failed"; break;
			case 6: o=@"Destination network unknown"; break;	case 7: o=@"Destination host unknown"; break;
			case 8: o=@"Source host isolated"; break;		case 9: o=@"Network administratively prohibited"; break;
			case 10: o=@"Host administratively prohibited"; break;	case 11: o=@"Network unreachable for TOS"; break;
			case 12: o=@"Host unreachable for TOS"; break;		case 13: o=@"Communication administratively prohibited"; break; } break;
		case 5: switch (h_icmp->icmp_code) {
			case 0: o=@"Redirect Datagram for the Network"; break;		case 1: o=@"Redirect Datagram for the Host"; break;
			case 2: o=@"Redirect Datagram for the TOS & network"; break;	case 3: o=@"Redirect Datagram for the TOS & host"; break; } break;
		case 11: switch (h_icmp->icmp_code) { case 0: o=@"TTL expired in transit"; break; case 1: o=@"Fragment reassembly time exceeded"; break; } break;
		case 12: switch (h_icmp->icmp_code) { case 0: o=@"Pointer indicates the error"; break; case 1: o=@"Missing a required option"; break; case 2: o=@"Bad length"; break; } break;
	}
	if (o) [n addChildWith:@"Code":o];
	[n addChildWith:@"Checksum":[NSString stringWithFormat:@"0x%04X",ntohs(h_icmp->icmp_cksum)]];
	[tree addChild:n]; [n release];
}

@end
